Android Tips #8 Android Lint の設定を理解する (1)

Android Tips #8 Android Lint の設定を理解する (1)

Clock Icon2012.02.09

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

変更履歴
2012/04/10 ADT17に対応しました。

はじめに

先日「Android Tips #7 Android Lint を活用する」で、Android Lintの使いかたをご紹介しました。
Android Lintでチェックする項目の有効/無効化は設定ウインドウ(環境設定 > Android > Lint Error Checking)で設定できるのですが、
現在は英語のみなので、パッと見わかりづらい印象を受けるかたも多いかと思います。
そこで、数回にわたりAndroid Lintでチェックできる項目を日本語で解説していきたいと思います。
今回はLintの設定方法、分類についての解説、各項目の解説(前編)です。

Lintの設定方法 - 起動タイミングとエラーの重大度を設定する

下図が設定ウインドウです。このウインドウでは、Lintの起動タイミングと各エラーの重大度が設定できます。

起動タイミングを設定する

設定ウインドウ上部に2つのチェックボックスがありますが、これでLintの起動タイミングの有効/無効が設定できます。
それぞれ以下のようなタイミングになります。

When saving files, check for errors
ファイルを保存するタイミングにエラーがないかチェックします。
Run full error check when exporting app
Androidアプリケーションをapkファイルとして書き出すタイミングでエラーがないかチェックします。
アプリの全ソースをチェックし、重大度が「Error」に設定されているエラーを発見すると書き出しを中止します。

各エラーの重大度を設定する

チェックする項目のリストの各項目にアイコンが表示されているのがわかりますが、これはエラーの重大度を表しています。
以下がそれぞれの重大度の詳細です。

重大度 アイコン 説明
(Default) - デフォルトの設定です。
Fatal エラー 特に重大なエラーとして表示されます。
この重大度に設定されているエラーが発見されるとビルドできなくなります。
Error エラー エラーとして表示されます。
この重大度に設定されているエラーが発見されるとビルドできなくなります。
Warning 警告 修正すべき警告として表示されます。自動修正できる警告は電球マークが付きます。
Information 情報 情報として表示されます。問題という認識はされません。
Ignore 無視 エラーを無視します。Lint Windowに表示されなくなります。

エラーの分類について

設定ウインドウでは、各エラーを以下のように分類されています。

分類名 分類名(日本語) 説明
Correctness 正確性 リソースファイルからリソースが正確に引き出せるかなどをチェックします。
Correctness:Message 正確性 主にStringリソースについて、正確な処理が行えるかチェックします。
Security セキュリティ アプリ設定などでセキュリティ上の問題がないかチェックします。
Performance パフォーマンス パフォーマンス低下のおそれがある問題がないかチェックします。
Usability:Typography ユーザビリティ(表記) Stringリソースなどで誤った表記がないかチェックします。
Usability:Icons ユーザビリティ(アイコン) アイコンが正常に表示されるリソース・設定になっているかチェックします。
Usability ユーザビリティ 操作性の低下につながる設定がないかチェックします。
Accessibility ユーザ補助 ユーザ補助のための設定に問題がないかチェックします。
Internationalization 国際化 多言語対応のアプリを作る上で配慮すべき、国際化に関するエラーです。

Lintのエラーを理解する(前編)

今回は前編ということで、Correctness、Securityに分類されている各エラーを解説したいと思います。

Correctness

SdCardPath

重大度 : Warning
優先度 : 6/10
概要 : SDカードパスの参照が "/sdcard" のようにハードコーディングされていないかチェックします。
自動修正 : なし
解説 :
ファイルの保存先にSDカードパスを参照することがよくありますが、
Javaソース上で "/sdcard" としてしまうと、端末によってはSDカードパスが異なる場合があるため正しく保存できません。
SDカードのパスは以下のメソッドで取得するようにしましょう。

Environment.getExternalStorageDirectory().getPath()

NewApi

重大度 : Error
優先度 : 6/10
概要 : 対象となるすべてのAPIのバージョンでサポートされていないAPIアクセスがないかチェックします。
自動修正 : なし
解説 :
アプリケーションが対象としているすべてのバージョンの中で利用できないAPIの呼び出しがないかチェックします。
上位バージョンのAPIの呼び出しが下位バージョンのAPIがサポートしていない場合、下位バージョンで正常に動作しません。

例えばActivityのアクションバーの取得はgetActionBar()メソッドを使いますが、
アクションバーは Honeycomb (APIレベル11) から追加されたので、
APIレベル11より下位のバージョンで実行すると NoSuchMethodError が送出されます。
このような場合、端末のバージョンを判別して処理を分ける必要があります。
下位バージョンでサポートされていないメソッドは処理を分けるようにしましょう。
また、処理を分けただけではこのエラーは消えないので注意してください。
ADT17からは、メソッドまたはクラスに @TargetApi アノテーション(または @SupressLint アノテーション)が付与できるようになりました。
処理を分けたあとは対象のメソッド、またはクラスに @TargetApi アノテーションを付けるようにしましょう。

DuplicateIncludedIds

重大度 : Warning
優先度 : 6/10
概要 : include tagを利用して結合したレイアウト間で同じIDが利用されているかをチェックします。
自動修正 : なし
解説 :
レイアウトXMLでは、たとえば以下のような書きかたで複数のレイアウトXMLファイルを組み合わせたレイアウトXMLファイルを作成できます。
このとき、includeタグで含めている複数のレイアウトXMLファイルの中で同じIDのViewが定義されていると警告してくれます。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    
    <include android:id="@+id/layout_a" layout="@layout/layout_a"/>
    <include android:id="@+id/layout_b" layout="@layout/layout_b"/>

</LinearLayout>

DuplicateIds

重大度 : Warning
優先度 : 7/10
概 要 : 単独のレイアウトの中で同じIDが利用されているかをチェックします。
自動修正 : なし
解 説 :
単独のレイアウトXMLファイルの中で、同じIDのViewがあるかどうかをチェックします。
例えば、ひとつのレイアウトXMLの中で同じIDが使用されたTextViewが複数あった場合、
findViewById()メソッドで特定のTextViewが正確に取得できなくなってしまいます。

UnknownId

重大度 : Fatal
優先度 : 8/10
概要 : RelativeLayoutの相対指定で使われているリソースIDがリソース内にあるかチェックします。
自動修正 : なし
解説 :
"@+id/" 属性は既存のidを参照する場合か、新しいidを定義する場合に使用します。
既存のidを参照する場合とは、RelativeLayoutで上下または左右のレイアウト上の関係性を示す場合のことです。
android:layout_above や android:layout_below 属性などで対象のViewのidを指定することで、
View全体を相対的にレイアウトすることができます。

この参照先のidがリソース上で定義されていない場合、エラーとなります。

タイプミス(Typo)の恐れがあることも考慮されており、
ヒントとして指定されたIDに近い名前の既存IDを表示してくれます。

UnknownIdInLayout

重大度 : Warning
優先度 : 5/10
概要 : "@+id/"で参照したリソースIDのViewがレイアウトXML内にあるかチェックします。
自動修正 : なし
解説 :
UnknownIdはidがすべてのリソースID内にない場合に警告されるのに対し、
UnknownIdInLayoutはレイアウトXML内に見つからない場合に警告されます。
("@+id/"で指定したIDの対象のViewが別なレイアウトXMLにある場合)

タイプミス(Typo)の恐れがあることも考慮されており、
ヒントとして指定されたIDに近い名前の既存IDを表示してくれます。

StateListReachable

重大度 : Warning
優先度 : 5/10
概 要 : selectorタグの中で到達できない状態(state)がないかチェックします。
自動修正 : なし
解 説 :
selectorタグはdrawableリソースのXMLファイルで使われます。
selectorタグの子にはitemタグでそれぞれの状態(例えばボタンであれば、通常の状態、押された状態など)を記述していきますが、
このitemタグの記述順として、state修飾詞が含まれていないものは最後に記述すべきです。
selectorファイルの中身は上から順番に読まれていき、上限が合致したものがあればそれ以降のitem要素は読まれません。
そのため条件(状態)指定が入っていないitemを先に書いてしまうと、
そのアイテムが条件に合致したファイルと扱われてしまいます。
つまり、通常の状態は最後のitemにすべき、ということになります。
例えば以下のように記述してしまうと、state指定がないitem以降の要素は到達することができません。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
  <item
    android:state_focused="true"
    android:state_enabled="true"
    android:state_pressed="false"
    android:drawable="@drawable/button_focused" />
  <!-- stateがないitem -->
  <item
    android:drawable="@drawable/button_default" />
  <!-- 以下の要素は到達できない -->
  <item
    android:state_focused="true"
    android:state_enabled="true"
    android:state_pressed="true"
    android:drawable="@drawable/button_pressed" />
  <item
    android:state_enabled="false"
    android:drawable="@drawable/button_disabled" />
</selector>

StyleCycle

重大度 : Fatal
優先度 : 8/10
概要 : スタイル定義のサイクルをチェックします。
自動修正 : なし
解説 :
このチェックでは、スタイル定義の継承関係に関するチェックを行います。

例えば、以下のようなスタイル定義を行ったとします。

<style name="CustomFont">
    <item name="android:textSize">20sp</item>
</style>
    
<style name="CustomFont.Red">
    <item name="android:textColor">#FF0000</item>
</style>

このように定義することで、 CustomFont.Red は CustomFont を継承している、という意味になります。
CustomFont の android:textSize 要素の値は CustomFont.Red のスタイルに引き継がれます。

問題は、以下のような定義を行った場合です。

<style name="CustomFont" parent="CustomFont.Red">
    <item name="android:textSize">20sp</item>
</style>
    
<style name="CustomFont.Red">
    <item name="android:textColor">#FF0000</item>
</style>

CustomFont の継承元(parent)に CustomFont.Red を指定しています。
しかし CustomFont.Red は CustomFont を継承しているため、継承関係に矛盾が生じてRuntimeExceptionが発生してしまいます。

このチェックでは、上記のように styleリソースの継承関係に矛盾がないかチェックします。
このエラーが出た場合は継承関係をもう一度確認してみましょう。

ScrollViewSize

重大度 : Warning
優先度 : 7/10
概 要 : ScrollViewのスクロール方向に対して、子のサイズ指定にwrap_contentが使われているかチェックします。
自動修正 : あり
解 説 :
ScrollViewは、レイアウト内をスクロールすることができるようにするViewです。
縦スクロールさせたい場合はScrollView、横スクロールさせたい場合はHorizontalScrollViewを使います。
ScrollViewの子にはViewを1つだけ配置することができますが、
子のサイズ指定(ScrollViewではlayout_height、HorizontalScrollViewではlayout_width)に
fill_parent(またはmatch_parent)ではなくwrap_contentを指定すべきです。

Deplicated

重大度 : Warning
優先度 : 2/10
概 要 : 推奨されていないリソースがないかチェックします。
自動修正 : なし
解 説 :
主にリソースファイル内で、現行のSDKバージョンでは推奨されていないコンポーネントなどがないかチェックします。
例えばAbsoluteLayoutというViewがありますが、これは現行のSDKバージョンでは非推奨なので警告されます。
非推奨のリソースは使ってもいいことがありませんので、もしこの警告が発生した場合は無視せずに別な方法で実装するようにしましょう。

NestedScrolling

重大度 : Warning
優先度 : 7/10
概 要 : スクロールできるViewの中にスクロールできるViewがないかチェックします。
自動修正 : なし
解 説 :
ScrollViewなどスクロールすることができるViewの中に、さらにスクロールできるViewを入れると警告されます。
例えば下図のように、ScrollViewで全体をスクロールさせておき、かつその中でListViewなどでスクロールさせるなどというような場合です。
動作することはするのですが、ユーザビリティ的に問題がある(とても使いづらい)ので使用すべきではありません。

ScrollViewCount

重大度 : Warning
優先度 : 8/10
概 要 : ScrollViewの中の子が1つだけしかないかチェックします。
自動修正 : なし
解 説 :
ScrollView(HorizontalScrollView)の子は原則的に1つのViewで作らなければいけません。
もしScrollViewの中に子を複数含めたい場合は、レイアウト系のView(LinearLayoutなど)1つにまとめるようにします。

良い例

<ScrollView android:layout_width="fill_parent"
            android:layout_height="wrap_content">
    <LinearLayout android:layout_width="fill_parent"
                  android:layout_height="wrap_content">
        <TextView android:layout_width="fill_parent"
                  android:layout_height="wrap_content" />
        <Button android:layout_width="fill_parent"
                android:layout_height="wrap_content"/>
    </LinearLayout>
</ScrollView>

悪い例

<ScrollView android:layout_width="fill_parent"
            android:layout_height="wrap_content">
    <LinearLayout android:layout_width="fill_parent"
                  android:layout_height="wrap_content">
        <TextView android:layout_width="fill_parent"
                  android:layout_height="wrap_content" />
    </LinearLayout>
    <Button android:layout_width="fill_parent"
            android:layout_height="wrap_content"/>
</ScrollView>

AdapterViewChildren

重大度 : Warning
優先度 : 10/10
概 要 : AdapterViewがレイアウトXML内で子として定義されているかどうかをチェックします。
自動修正 : なし
解 説 :
AdapterViewとは、ListViewなどの配列を表示するViewのことです。
レイアウトXML内でAdapterViewの子のViewをネストしていると警告されます。
AdapterViewの子(つまり、アイテムレンダラ)は原則的にJava側でAdapterを実装する必要があるので、
レイアウトXML内では子を定義できません。

GridLayout

重大度 : Error
優先度 : 4/10
概 要 : GridLayoutの子の行と列の定義が範囲外になっていないかチェックします。
自動修正 : なし
解 説 :
GridLayoutはAndroid4.0(IceCreamSandwich)で新たに追加されたレイアウトViewです。
GridLayoutの作りかたは、まず android:rowCount プロパティ、 android:columnCount プロパティで全体の行、列を定義し、
子の android:layout_row プロパティ、 android:layout_column プロパティで、親から見てどの場所に配置するか指定します。
図で示すと以下のようになります。

このとき、子の android:layout_row プロパティ、 android:layout_column プロパティの値が
親で定義されている android:rowCount プロパティ、 android:columnCount プロパティより大きな値(=範囲外となる値)が
指定されていないかをチェックします。デフォルトの重大度はErrorになっているのでビルドできなくなります。
ちなみに子の android:layout_row プロパティ、 android:layout_column プロパティ は 0 からスタートなので注意が必要です。

OnClick

重大度 : Error
優先度 : 10/10
概要 : android:onClick 属性で定義されたメソッドがあるかチェックします。
Ensures that onClick attribute values refer to real methods
自動修正 : なし
解説 :
レイアウトXML内の View には android:onClick 属性を使って、
クリックしたときのイベントハンドラを以下のように定義することができます。

レイアウト

<Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="onButtonClick"
    />

レイアウトの適用先のJavaソース

public void onButtonClick(View view) {
        // ボタンがクリックされたときの処理
    }

この定義されたメソッドがJavaソース内に存在するかチェックしてくれます。

ちなみに、Javaソース全体からメソッドの有無を判断しているようなのでご注意ください。
setContentView() されているActivityと紐づいているわけではないようです。

Registered

重大度 : Warning
優先度 : 6/10
概要 : Activity, Service, ContentProvider が AndroidManifest.xml に定義されているかチェックします。
自動修正 : なし
解説 :
Activityクラス, Serviceクラス, ContentProviderクラスはAndroidManifest.xmlに定義されていないと
呼び出すことができません(コンパイルエラーにはならず、実行時に例外として処理されます)。
例えばActivityクラスを新規作成し、そのActivityをstartActivity()で実行しようとすると、
AndroidManifest.xml内に定義されていない場合 ActivityNotFoundException が送出されてしまいます。
(このエラーは誰もが経験していると思うのは私だけでしょうか…。)
このチェックは上記のような場合に警告され、対象のクラス名を表示してくれます。
Activityクラス, Serviceクラス, ContentProviderクラスを新しく作成した場合は
確実にAndroidManifest.xmlに定義するようにしましょう。

※ Activityクラスを実際のActivityの親クラスとして使っているときに警告が出てしまう場合は
抽象クラス(abstract class)にするようにしましょう。

Proguard

重大度 : Error
優先度 : 8/10
概 要 : proguard.cfg ファイルに問題がないかチェックします。
自動修正 : なし
解 説 :
Proguard は、アプリのリリースビルド時にソースを難読化(逆コンパイルなどによるプログラムの解読を防ぐ)したり、
コメントアウトなどの余計なコードを除き軽量化したりしてくれる便利なツールです。
設定は proguard.cfg ファイルに記述します(Androidプロジェクト作成時に自動生成されます)。
この proguard.cfg ファイルの設定に問題がないかチェックし、問題を発見した場合はエラーとして扱われます。
proguard.cfg ファイルでは、-keepオプションで指定したクラスやメソッドなどをProguardの対象から外すことができます。
この-keepオプションですが、具体的に以下のような種類があります。

-keep 指定のクラスとクラス・メンバーにProguardをかけない
-keepclassmembers 指定のクラス・メンバーにProguardをかけない
-keepclasseswithmembers 指定のクラスとクラス・メンバーにProguardをかけない
(クラス・メンバーがある場合のみ)
-keepnames 指定のクラスとクラス・メンバーにProguardをかけない
(ダウンサイジング時に削除されていない場合)
-keepclassmembernames 指定のクラス・メンバーにProguardをかけない
(ダウンサイジング時に削除されていない場合)
-keepclasseswithmembernames 指定のクラスとクラス・メンバーにProguardをかけない
(クラス・メンバーがあり、かつダウンサイジング時に削除されていない場合)

この中の -keepclasseswithmembernames オプションですが、
最新のADTでは代わりに -keepclasseswithmembers オプションを使うことが推奨されています。
詳しい情報はこちらに載っているのでご参照ください。

ProguardSplit

重大度 : Warning
優先度 : 3/10
概要 : プロジェクトのProGuard設定が正しく設定されているかチェックします。 自動修正 : なし
解説 :
以前のADTバージョン(ADT16以下のバージョン)でプロジェクトに ProGuard を設定するには、
以下のように project.propertiesファイル内に "proguard.cfg" ファイルのパスを記述していました。

proguard.config=proguard.cfg

これまではプロジェクト毎に単独のProGuard設定ファイルを記述しなければいけませんでしたが、
新しいADTバージョン(ADT17以降)ではProGuardの設定ルールに変更があり、設定ファイルが以下のように二分割されました。

  • プロジェクト固有のフラグを含むシンプルな設定ファイル
  • Androidプロジェクトに推奨される設定値を含む一般的な設定ファイル
    (この設定ファイルはSDKのインストールディレクトリにあり、SDKのアップデートで管理されます)

この設定を有効にするには、プロジェクト用の設定ファイル(project.properties)内の proguard.config プロパティを
次のように設定します。

proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt

proguard-android.txt というファイルが共通化された一般的な推奨設定ファイル、
proguard-project.txt がプロジェクトでカスタマイズできる設定ファイル(以前のバージョンの proguard.cfg ファイルにあたる)になります。
また、さらに設定ファイルを追加することで一つのプロジェクトに対して複数のカスタム設定ファイルが適用できます。
※ proguard-project.txt ファイルは新規プロジェクト作成時に自動的に作成されます。
この設定ルールにより、特に意識しなくても推奨の設定が利用できるようになりました。

このチェックでは、プロジェクトのProGuard設定が以前のADTバージョンの設定になっている場合に警告してくれます。
警告が出た場合は新しいADTバージョンの設定ルールに従って設定を変更しましょう。

PxUsage

重大度 : Warning
優先度 : 2/10
概 要 : 単位に"px"が使われているかチェックします。
自動修正 : あり
解 説 :
レイアウトXMLのサイズのプロパティ指定に"px"(ピクセル)が使われているかどうかをチェックします。
Androidでは、"px"の指定は推奨されていません。これはAndroid端末の画面はさまざまな画面サイズ・解像度があるため、
絶対値で指定してしまうとレイアウトが崩れてしまう可能性が大いにあるからです。
通常は"fill_parent"や"dip"(解像度によって相対的に変化する単位)などのような指定が理想とされています。
しかしパフォーマンス上の問題もあるため(相対的な指定のほうがメモリを消費します)、
一概にすべて相対的指定すべきというわけでもありません。
どうしても"px"で指定したい場合は、画面サイズに応じてスケールするしくみを実装する必要があります。

ちなみにPxUsageには自動修正があり、下記の画面から解像度を選択することで
"px"指定がされているプロパティを選択した解像度にあった"dip"指定に修正してくれます。

選択できる解像度は以下のとおりです。

名前 解像度(DPI) 代表的な端末
X-High Density 320dpi Galaxy Nexus, IS03 など
High Density 240dpi Nexus One, Galaxy S, Xperia arc など
TV Density 213dpi Google TV など
Medium Density 160dpi Motorola Xoom, SonyTablet S, OptimasPad など
Low Density 120dpi Xperia10 MiniPro など

TextViewEdits

重大度 : Warning
優先度 : 7/10
概要 : 入力(Input)に利用されているTextViewがないかチェックします。
自動修正 :
解説 :
android:editable="true" などのように、TextViewに入力用プロパティがセットされるとエラーになる可能性があるので、
TextViewに入力させたい場合、Viewクラスを EditText に変更すべきです。
EditText は TextView に入力機能を加えたサブクラスです。入力には EditText を利用するようにしましょう。

ExtraText

重大度 : Warning
優先度 : 3/10
概要 : 無関係なテキストがレイアウトXMLにないかチェックします。
自動修正 : なし
解説 :
レイアウトXML内には属性と要素のみ記述しなければいけません。
属性と要素以外のテキストがあると致命的な問題になる可能性があります。
コメントなどを記述したい場合はコメントアウトを使用するようにしましょう。

PrivateResource

重大度 : Fatal
優先度 : 3/10
概要 : プライベートなリソースが参照されていないかチェックします。
自動修正 : なし
解説 :
AndroidSDKのリソースの参照するには、 @android:drawable/* のように記述します。
例えば以下では、ButtonのアイコンにAndroidSDKのdrawableリソース内の検索アイコンを参照しています。

<Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:drawableLeft="@android:drawable/ic_menu_search"
    />

上記のようなリソースはAndroidSDKでパブリックなリソースとして提供されているものです。
@*android:drawable のように記述することでプライベートなリソースを参照することができますが、
プライベートリソースは、予告なしに消えることがあるため参照すべきではありません。
プライベートなリソースは参照しないようにしましょう。

InconsistentArrays

重大度 : Warning
優先度 : 7/10
概 要 : 配列の要素数に矛盾がないかチェックします。
自動修正 : なし
解 説 :
strings.xmlなどのリソースファイルでは、 strings-array で string を配列としてまとめることができます。
以下のように記述し、Java側では getResources().getStringArray() メソッドでString配列として受け取ることができます。

<string-array name="test">
    <item>アイテム1</item>
    <item>アイテム2</item>
    <item>アイテム3</item>
    <item>アイテム4</item>
</string-array>

String[] test = getResources().getStringArray(R.array.test);

このstring-array の中の要素の数が、言語ごとに同じになっているかどうかチェックします。
警告が出た場合「どの言語のどの配列が何個か」説明してくれるので確認しやすいと思います。

しかし、場合によっては「この言語だけこの数にしたい」ということもあります。
そのときはエラーかどうか自分で判断し、必要なければLintの設定を変更しましょう。

ManifestOrder

重大度 : Warning
優先度 : 5/10
概 要 : マニフェストファイルの設定の記述順序に問題がないかチェックします。
自動修正 : なし
解 説 :
AndroidManifest.xml は、アプリの設定を記述するマニフェストファイルです。
その中で使われる "application" タグは "uses-sdk" タグや "uses-permission" タグのあとに書くべきです。
設定ファイルは上から順に見ていくため、アプリに必要な情報の順序で記述していかなければ
バグになってしまうことがあるようです。ロジカルな順序で記述していくことが推奨されています。
"application" タグより前に記述すべき要素は以下のとおりです(実際にLintでエラーが出るかどうか調べました)。

タグ名 設定の概要
uses-permission アプリで使用したい許可(インターネット接続やGPS、ファイルアクセスなど)を指定します。
permission 他のアプリからこのアプリの特定のコンポーネントに対するアクセス制限を定義します。
permission-tree アクセス許可のグループに対するネームスペースを定義します。
permission-group アクセス許可をグループ化したいときに、グループを定義します。
uses-sdk ターゲットのAPIレベル(バージョン)、最小APIレベル、最大APIレベルを指定します。
uses-configuration アプリが必要としている、ハードウェアとソフトウェアの仕様を指定します。
uses-feature アプリが使用する機能を宣言します。
supports-screens アプリがサポートする解像度と密度調整を指定します。
compatible-screens マーケットでフィルタリングするための、アプリがサポートするスクリーンサイズと解像度を指定します。
supports-gl-texture アプリがサポートするOpenGL テクスチャ圧縮フォーマットを指定します。

UsesMinSdkAttributes

重大度 : Warning
優先度 : 2/10
概要 : 最小SDKバージョンとターゲットSDKバージョンが定義されているかチェックします。
自動修正 : なし
解説 :
Androidアプリを作成する上で、AndroidManifest.xmlの uses-sdk 属性に
android:minSdkVersion (最小SDKバージョン) と android:targetSdkVersion (ターゲットSDKバージョン) を指定すべきです。
この指定がない場合、どのバージョンの端末にもインストールできるようになってしまうので、
想定外のバージョンの端末で使われ、思わぬ障害を起こしてしまう可能性があります。
また、ここで指定した内容はAndroidマーケットに登録するときにアプリの詳細情報として表示されるため
ユーザは自分の端末バージョンに適しているかどうか判断することができます。
android:minSdkVersion と android:targetSdkVersion は必ず指定するようにしましょう。

MultipleUsesSdk

重大度 : Error
優先度 : 6/10
概要 : uses-sdk 要素が複数指定されていないかチェックします。
自動修正 : なし
解説 :
AndroidManifest.xml の uses-sdk 要素は複数指定することはできません。
複数記述してもマージは行われず、一つの要素だけが有効になります。
uses-sdk 要素は1つだけ指定するようにしましょう。

MissingPrefix

重大度 : Warning
優先度 : 8/10
概 要 : リソースXMLファイルで指定されている属性にネームスペースが使われているかチェックします。
自動修正 : あり
解 説 :
レイアウトXMLなどで宣言されているViewの属性(プロパティ)は、
android:layout_width などのように「 ネームスペース : プロパティ名 」の形式で記述します。
MissingPrefixは、ネームスペースを省略して書いてしまっていないかチェックしてくれます。
ネームスペースを記述しないと「カスタムのプロパティ」として認識されてしまうので、
コードヒントを活用してネームスペースを必ず記述するようにしましょう。
自分でカスタムViewやカスタム属性を定義していると間違いがちなので注意が必要です。

WrongViewCast

重大度 : Error
優先度 : 9/10
概要 : レイアウトXMLで定義されている View が別な型でキャストされていないかチェックします。
自動修正 : なし
解説 :
Javaソース側からレイアウトXML内のViewを参照する場合、基本的に同じ型でキャストします。
例えば、以下のようにレイアウトXMLでは Button 要素となっている View (R.id.button) を
Java側で ImageView にキャストしようとするとエラーとなります。

<Button 
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/button"
    />

ImageView imageView = (ImageView) findViewById(R.id.button);

レイアウトXMLの View をJava側で取得するときには同じ型にキャストするようにしましょう。

SuspiciousImport

重大度 : Warning
優先度 : 9/10
概要 : android.R がインポートされていないかチェックします。
自動修正 : なし
解説 :
Java側からのリソースの参照は R クラスを使用しますが、
この R クラスは AndroidSDK の R クラスと 自分のアプリ側の R クラスの2つがあります。
ソース上で判別しやすくするように、AndroidSDKのリソース参照は android.R とすることが推奨されます。
しかし、IDEの自動インポート機能によって AndroidSDK の R クラスをインポートされてしまうと
R の参照がAndroidSDKのリソースになってしまうため、AndroidSDKのリソースか自分のリソースか判別しづらくなってしまいます。
そのため、このチェックでは android.R クラスがインポートされていないかチェックしてくれます。
AndroidSDKのリソースは必ず android.R で参照するようにしましょう。

LibraryCustomView

重大度 : Error
優先度 : 6/10
概要 : ライブラリのレイアウトXML内で、カスタム属性の名前空間に "http://schemas.android.com/apk/res-auto" を使用しているかチェックします。
自動修正 : なし
解説 :
ライブラリプロジェクトには、カスタムViewとカスタム属性を含めることができます。
またレイアウトXMLなどのリソースファイルも含めることができ、アプリプロジェクト側から参照することもできます。
このライブラリプロジェクトのレイアウトXMLでカスタムViewとカスタム属性を使用する場合、
カスタム属性の名前空間のURIは "http://schemas.android.com/apk/res-auto" を指定しなければいけません。
上記のURIを使用することで、アプリプロジェクトのリソースとライブラリのリソースを自動的にマージしてくれます。
ライブラリプロジェクトの名前空間のURIは http://schemas.android.com/apk/res-auto を使用するようにしましょう。

UnusedNamespace

重大度 : Warning
優先度 : 1/10
概要 : 使用されていない名前空間がないかチェックします。
自動修正 : なし
解説 :
レイアウトXML内に未使用の名前空間がある場合に警告してくれます。
不要な名前空間は削除するようにしましょう。

NamespaceTypo

重大度 : Warning
優先度 : 8/10
概要 : 名前空間のURIにタイプミス(Typo)がないかチェックします。
自動修正 : なし
解説 :
このチェックでは、レイアウトXMLで使用される名前空間のURI "http://schemas.android.com/apk/res/android" にタイプミスがある場合に警告してくれます。
レイアウトXMLを新規作成するときには名前空間の宣言は自動的に記述されるのでタイプミスを生じることは少ないように思われますが、
もしタイプミスが生じた場合、いくつかの非常にあいまいなエラーメッセージにつながる可能性があります。
この警告が出たときには名前空間のURIを "http://schemas.android.com/apk/res/android" に修正するようにしましょう。

ResourceAsColor

重大度 : Error
優先度 : 7/10
概要 : setColor()メソッドでリソースIDが使用されていないかチェックします。
Looks for calls to setColor where a resource id is passed instead of a resolved color
自動修正 : なし
解説 :
setColor()メソッドの引数にはリソースIDではなく直接の色のIntegerを渡さなければいけません。 リソースIDから取得したい場合は以下のように記述します。

Paint の場合の例

Paint paint = new Paint();
paint.setColor(getResources().getColor(R.color.black));

Correctness:Message

StringFormatInvalid

重大度 : Error
優先度 : 9/10
概要 : String.format() で使用するStringリソースに無効な値がないかチェックします。
自動修正 : なし
解説 :
String.format() メソッドを使うことで、 '%' を入れた箇所に特定の値を動的に入れることができます。
このメソッドに用いるStringは、以下のようにStringリソースに定義することができます。

strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="sample" >今月は %s 月です。</string>
</resources>

Javaソース

String text = getResources().getString(R.string.sample);
String month = "4";
String str = String.format(text, month);
TextView textView = (TextView) findViewById(R.id.text_view);
textView.setText(str);

実行結果

このチェックでは、フォーマットに関する以下の2つの問題を調べます。

  • Stringリソースがフォーマット記法に従っているかチェックします。
    例えば '% s' のように、間に空白文字が入っていたりすると正しくフォーマットできません。
  • Stringリソースにフォーマットしない '%' を含んでいるかチェックします。
    Stringリソースにフォーマットしない % を含みたい場合は '%%' と記述する必要があります。

注:
フォーマット書式に沿っているStringリソースはすべて String.format() を使用するためのものという認識になります。
例えば、 android.text.format.Time#format に使うStringは '%Y' などように記述しますが、
Lintではその形式を判断することができませんので、誤った警告が出てしまいます。
その場合のエラーを抑制する方法については、抑制するヘルプトピックを参照してください。

StringFormatCount

重大度 : Warning
優先度 : 5/10
概要 : String.format() で使用するStringに出力するStringの数が合っているかチェックします。
自動修正 : なし
解説 :
String.format() メソッドを使用してStringに複数の値を代入することができますが、
このチェックではStringに出力値の数が合っていない場合警告してくれます。
例えば以下のように、Stringに複数の値を代入するには String.format メソッドの第二引数以下に値を入れていきます。

strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="sample">今日は %s 月 %s 日です。</string>
</resources>

Javaソース

String text = getResources().getString(R.string.sample);
String month = "4";
String day = "2";
String str = String.format(text, month, day);

上記の場合、 R.string.sample の値に '%' が2つ含まれていて、
String.format() の引数に 2つの値(month と day)を入れているので正しくフォーマットされます。
しかし値の数があっていない場合、正しくフォーマットできないためエラーとなります。
このエラーが出た場合はフォーマットに使用する値の数を確認しましょう。

StringFormatMatches

重大度 : Error
優先度 : 9/10
概要 : Stringリソースの定義が String.format() の呼び出しに適合してるかチェックします。
自動修正 : なし
解説 :
String.format() で使用するStringリソースの型について、以下の2点をチェックします。

言語別Stringリソースの型

String.format() で使用されるStringリソースの書式 '%' は、
'%s' (String) や '%d' (十進数) などのように値の型を定義することができます。
この書式が、複数の言語のstring.xml内の同じリソースで同一かどうかをチェックします。
例えば日本語(ja)の string.xml では '%d'、英語(en)の string.xml では '%s' ですと
型が違うため、エラーとなります。 String.format() で内の代入箇所と代入値の型が同一か否かをチェックします。
例えば '%d'(整数) の指定に 文字列を代入しようとした場合はエラーとなります。

StringリソースとJavaソースの型

Javaソースで String.format() で使用されるStringリソースの書式の型と、出力値の型が同一かどうかをチェックします。
以下の例のように、書式が '%s' で指定されているのに、String.format()の第二引数が
int型を指定すると、型が違うためエラーとなります。

Stringリソース

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello, %s</string>
</resources>

Javaソース

String text = getResources().getString(R.string.sample);
String month = 4;
String str = String.format(text, month);

ExtraTranslation

重大度 : Warning
優先度 : 6/10
概 要 : ある特定の言語のリソースファイルにある文字列のリソースが、デフォルトのリソースファイルにあるかどうかをチェックします。
自動修正 : なし
解 説 :
Androidアプリでは、アプリ内に表示する文字をXMLで管理し、言語設定に応じて
リソースファイルを切り替えることで多言語対応(国際化)を実現しています。
文字列のリソースファイル(strings.xml)が言語ごとに分かれることになりますが、
それぞれのリソースファイル内の要素数は同じになるはずです。
しかし、ある特定の言語のリソースファイルにのみ定義されている要素がある場合、
デフォルトのリソースや他の言語のリソースからは読めません。
レイアウトXMLから参照すると、リソースのIDが表示されてしまいます(下図参照)。
予期しないバグになることもあるので、もしこのエラーが出る場合はそれぞれの言語のリソースファイルを確認するようにしましょう。

MissingTranslation

重大度 : Error
優先度 : 8/10
概 要 : すべての文字列のリソースの翻訳が正確に行われるかどうかをチェックします。
自動修正 : なし
解 説 :
それぞれの言語ごとの文字列リソースファイル(strings.xml)の各要素をチェックし、翻訳されるかどうかをチェックします。
上記「ExtraTranslation」と反対に、デフォルトのリソースファイルに定義されている要素が、
いずれかの言語のリソースファイルに定義されていない場合にエラーとなります。
デフォルトの重大度は「Error」となっているので、このエラーが発見されるとビルドできなくなります。

Security

HardcodedDebugMode

重大度 : Warning
優先度 : 5/10
概要 : android:debuggable 属性がハードコーディングされていないかチェックします。
自動修正 : なし
解説 :
最新のADT(17)では、エミュレータまたはデバイス上でデバッグするAPKファイルをビルドするときに
ツールが自動的に android:debuggable="true" を挿入するようになっています。
また、リリースビルド時には android:debuggable="false" が自動的に挿入されるようになったので、
開発者側で android:debuggable 属性を意識する必要がなくなりました。
そのため、AndroidManifest.xml から android:debuggable 属性を除外することが推奨されています。

しかし AndroidManifest.xml に android:debuggable 属性が記述されている場合、ツールは常にその値を使用します。
そのため、例えば android:debuggable="true" と記述してしまうと
デバッグバージョンをリリースビルドし、マーケットに公開してしまう可能性があります。
この警告が出たら AndroidManifest.xml から android:debuggable 属性を除外するようにしましょう。

ExportedService

重大度 : Warning
優先度 : 5/10
概 要 : パーミッションを必要としない ExportedService (android:exported="true"に設定されているService)がないかチェックします。
自動修正 : なし
解 説 :
Serviceクラスを使用する場合、以下のように AndroidManifest.xml ファイルの "application" タグ内に
"service" タグを記述し、Serviceクラスを登録します。

<service android:name=".SampleService" />

この "service" 要素に android:exported 属性を追加、trueと設定すると
Serviceクラスを他のアプリから呼び出すことができるようになります。
この設定にする場合パーミッション設定が必要となりますが、この設定がされているかどうかをチェックしてくれます。
パーミッション設定がないと他のアプリからServiceクラスを呼び出すことができなくなってしまうので、
Serviceクラスを公開したい場合は必ずパーミッションを設定するようにしましょう。

GrantAllUris

重大度 : Warning
優先度 : 7/10
概 要 : 共有設定されている "grant-uri-permission" 要素をチェックします。
自動修正 : なし
解 説 :
"grant-uri-permission" 要素はAndroidManifest.xml内に記述する設定で、
アプリで保存しているデータ(ContentProvider)への外部からのアクセスを個別に設定するときに使用します。

ContentProvider の外部からのアクセスを許可するには、以下のような設定を "application" タグ内に記述します。

<provider android:name="TestProvider"
          android:authorities="jp.classmethod.sample.testprovider"
          android:readPermission="true"
          android:grantUriPermissions="false">
          <grant-uri-permission android:pathPrefix="/test" />
</provider>

この中の android:grantUriPermissions を true に設定すると、ContentProviderの配下にあるあらゆるデータに対し許可を付与できます。
逆に false に設定すると、 "grant-uri-permission" で特定されたデータにのみアクセスを許可できます。
"grant-uri-permission" の属性には、一つの要素に対して以下のいずれかの要素をひとつだけ設定します(属性自体は複数設定できます)。

android:path アクセスを許可するパスのフルパスを指定します。
android:pathPrefix アクセスを許可するパスの最初の部分を指定します。
android:pathPattern アクセスを許可するパスの完全なパスを指定します(ワイルドカード指定可)。

いずれもアクセスを許可するパスの指定になりますが、単に "/" のみを指定してしまうと、
ContentProviderで保存しているすべてのデータにアクセスできてしまいます。
このような問題を防ぐため、Lintではセキュリティ的に問題がないかチェックしてくれます。

WorldWriteableFiles

重大度 : Warning
優先度 : 4/10
概要 : openFileOutput() メソッドで MODE_WORLD_WRITEABLE が渡されていないかチェックします。
自動修正 : なし
解説 :
openFileOutput() メソッドは、ファイルに文字データやバイナリデータを書き込むときに使用するメソッドです。
このメソッドでは、以下のいずれかの書き込みモードを指定します。

MODE_APPEND 現在のファイルに追加で書き込みます。
MODE_PRIVATE このアプリのみで使用できるモードで書き込みます。
MODE_WORLD_READABLE 他のアプリから読み込みが可能なモードで書き込みます。
MODE_WORLD_WRITEABLE 他のアプリから読み書きが可能なモードで書き込みます。

このうち MODE_WORLD_WRITEABLE のモードでファイルを書き込む場合、
どのようなアプリからでも読み書き可能なファイルということになりますので、
もし悪意のあるアプリによってファイルが変更されてしまった場合、
アプリからファイルを読み込むときに正常に動作しないという危険性があります。

そのため、 MODE_WORLD_WRITEABLE モードで書き込むかどうかは慎重に精査する必要があります。
MODE_WORLD_WRITEABLE 使用する場合は、適切なファイルかどうかを判断する何かしらの対策を取るようにしましょう。

まとめ

今回はAndroid Lintの設定について、設定方法とエラーの分類、そして各エラーの意味について解説しました。
Lintがチェックしてくれる項目は健全なアプリにする上で貴重な情報が載っているので、個人的にはかなり勉強になりました。
残りのエラーの解説は後編として近日執筆したいと思います。

参考

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.